/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved.
 *
 */

import path from 'path';

import { HookType } from '../../../../common/const/hook-const.js';
import { noop } from '../../../../common/util/noop.js';
import { Queue } from '../../../../common/util/queue.js';
import { ClassTypeConst } from '../../../common/options/class-type-const.js';
import { HvigorCommonConst } from '../../../common/options/common-const.js';
import { HvigorCoreNode, Module, Project } from '../../../external/core/hvigor-core-node.js';
import { hvigorCore } from '../../../external/core/hvigor-core.js';
import { Consumer } from '../../../external/interface/consumer.js';
import { DefaultPluginContainer } from '../../../external/plugin/default-plugin-container.js';
import { HvigorSystemPlugin } from '../../../external/plugin/hvigor-system-plugin.js';
import { CoreTask } from '../../../external/task/core-task.js';
import { HvigorLogger } from '../../../log/hvigor-log.js';
import { HvigorNode } from '../../../vigor/plugin/interface/hvigor-node.js';
import { Config, CoreConfig } from '../../../vigor/plugin/interface/loader-profile.js';
import { PluginContext } from '../../../vigor/plugin/interface/plugin-context.js';
import { TransformHvigorTask } from '../../../vigor/task/impl/transform-hvigor-task.js';
import { HvigorTask, HvigorTaskContext } from '../../../vigor/task/interface/hvigor-task.js';
import { DefaultInternalTask } from '../../task/core/default-internal-task.js';
import { LazyTaskContainer, TaskCreation } from '../../task/core/lazy-task-container.js';
import { TaskDirectedAcyclicGraph } from '../../task/core/task-directed-acyclic-graph.js';
import { TaskContainer } from '../../task/interface/task-container-interface.js';
import { TaskDetails } from '../../task/interface/task-details-interface.js';
import { union } from '../../task/util/task-path-util.js';

/**
 * hvigor中module的默认公共父类
 *
 * @since 2022/1/8
 */
export abstract class DefaultNodeImpl implements HvigorCoreNode {
  classKind = ClassTypeConst.HVIGOR_NODE;
  protected readonly _nodeName: string;
  protected readonly _nodePath: string;
  protected readonly _packageJsonPath: string;
  protected readonly _buildFilePath: string;
  protected readonly _buildProfilePath: string;
  protected readonly _tasks: LazyTaskContainer;

  protected readonly _taskGraph: TaskDirectedAcyclicGraph;
  protected readonly _pluginContainer: DefaultPluginContainer;
  protected readonly _contextInfo: PluginContext;
  protected readonly _currentNodeLoaderPluginIds: string[];
  protected readonly taskMap: Map<CoreTask, CoreTask[]> = new Map<CoreTask, CoreTask[]>();
  private readonly extraOption: Map<string, any> = new Map();
  private logger: HvigorLogger = HvigorLogger.getLogger(DefaultNodeImpl.name);
  private configOpt: any;

  // 配置阶段hook
  private beforeNodeEvaluateFnQueue: Queue<Consumer<HvigorNode>> = new Queue();
  private afterNodeEvaluateFnQueue: Queue<Consumer<HvigorNode>> = new Queue();

  // 添加一个在解析系统插件后的hook 内部hook不对外提供
  private afterBindSystemPluginsFnQueue: Queue<Consumer<HvigorNode>> = new Queue();

  protected constructor(nodeName: string, nodePath: string) {
    this._nodeName = nodeName;
    this._nodePath = nodePath;
    this._packageJsonPath = path.resolve(nodePath, HvigorCommonConst.PACKAGE_JSON);
    this._buildFilePath = path.resolve(nodePath, HvigorCommonConst.BUILD_FILE_NAME);
    this._buildProfilePath = path.resolve(nodePath, HvigorCommonConst.BUILD_PROFILE_FILE);
    this._taskGraph = new TaskDirectedAcyclicGraph();
    this._tasks = new LazyTaskContainer(nodeName);
    this._pluginContainer = new DefaultPluginContainer();
    this._currentNodeLoaderPluginIds = [];
    this._contextInfo = new PluginContext();
  }

  /**
   * 从TaskContainer获取的任务没有模块信息, 组合模块信息后返回
   */
  getTaskPaths(): string[] {
    const node = this.classKind === ClassTypeConst.HVIGOR_PROJECT ? '' : this._nodeName;
    return this._tasks.getTaskPaths().map<string>((task) => union(node, task));
  }

  getTaskDepends(task: string): string[] {
    return this._tasks.getTaskDepends(task);
  }

  /**
   * 获取Module的build-profile.json5路径
   *
   * @return {string} build-profile.json5 path
   */
  getBuildProfilePath(): string {
    return this._buildProfilePath;
  }

  /**
   * 获取构建的hvigorfile.js路径
   *
   * @return {string}
   */
  getBuildFilePath(): string {
    return this._buildFilePath;
  }

  /**
   * 获取Node的路径
   *
   * @return {string}
   */
  getNodeDir(): string {
    return this._nodePath;
  }

  /**
   * 获取模块的名称
   *
   * @return {string}
   */
  getName(): string {
    return this._nodeName;
  }

  /**
   * 获取模块下的package.json路径
   *
   * @return {string}
   */
  getPackageJsonPath(): string {
    return this._packageJsonPath;
  }

  /**
   * 绑定具体的Plugin对象到Module对象上
   *
   * @param {HvigorSystemPlugin} plugin
   * @return {HvigorSystemPlugin}
   */
  bindPlugin(plugin: HvigorSystemPlugin): HvigorSystemPlugin {
    return this._pluginContainer.registryPlugin(plugin);
  }

  /**
   * 获取当前Node的任务DAG图对象
   *
   * @param {string} pluginId 插件标识
   * @param {Function} func context Function
   */
  bindPluginContextFunc(pluginId: string, func: Function): void {
    this._contextInfo.setContextFunc(pluginId, func);
    this._currentNodeLoaderPluginIds.push(pluginId);
  }

  /**
   * 获取Module对象的plugin对象
   *
   * @param {string} pluginId
   * @returns {HvigorSystemPlugin | undefined}
   */
  getPluginById(pluginId: string): HvigorSystemPlugin | undefined {
    return this._pluginContainer.getPluginById(pluginId);
  }

  getContext(pluginId: string): any {
    return this._contextInfo.getContext(pluginId);
  }

  getAllPluginIds(): string[] {
    return this._currentNodeLoaderPluginIds;
  }

  /**
   * 获取当前module注册的所有task
   *
   * @return {CoreTask[]} 任务列表
   */
  getAllTasks(): CoreTask[] {
    return this._tasks.getAllTasks();
  }

  /**
   * 通过taskName获取当前module的Task对象
   *
   * @param {string} name 任务名
   * @return {CoreTask | undefined}
   */
  getTaskByName(name: string): CoreTask | undefined {
    return this._tasks.findTask(name);
  }

  /**
   * 通过taskName获取当前module的lazy task的creationAction对象
   *
   * @param {string} name 任务名
   * @return {TaskCreation | undefined}
   */
  getTaskCAByName(name: string): TaskCreation | undefined {
    return this._tasks.findTaskCA(name);
  }

  /**
   * 通过需要执行的Function和任务名注册到当前Node中
   * 可以通过传入一个TaskDetails类型的对象，直接对Task的基础信息进行赋值
   *
   * @param {Function} fn
   * @param {string | TaskDetails} taskInfo
   * @return {CoreTask}
   */
  task(fn?: Function, taskInfo?: string | TaskDetails): CoreTask {
    let taskDetails: TaskDetails;

    if (taskInfo === undefined || typeof taskInfo === 'string') {
      taskDetails = {
        name: taskInfo ? taskInfo : 'default',
      };
    } else {
      taskDetails = taskInfo as TaskDetails;
    }

    const taskFunction: Function = fn ? fn! : noop;
    const task = new DefaultInternalTask(this, taskDetails, taskFunction);
    this._tasks.addTask(task);
    return task;
  }

  /**
   * 直接通过Task类型的对象注册任务到当前Node中
   * 可以通过传入一个TaskDetails类型的对象，直接对Task的基础信息进行赋值
   *
   * @param {CoreTask} task
   * @return {CoreTask}
   */
  registry(task: CoreTask): CoreTask {
    this._tasks.addTask(task);
    return task;
  }

  /**
   * 注册
   *
   * @param {HvigorTask} task
   * @return {HvigorTask}
   */
  registerTask(task: HvigorTask): HvigorTask {
    const pluginTask = new TransformHvigorTask(this, task);
    pluginTask.initTaskContext();
    pluginTask.initDependency();
    pluginTask.initTaskRun();
    this._tasks.addTask(pluginTask);
    return task;
  }

  getTaskContext() {
    const taskContext: HvigorTaskContext = {
      moduleName: this.getName(),
      modulePath: this.getNodeDir(),
    };
    return taskContext;
  }

  /**
   * 判断该module是否包含对应的task
   *
   * @param {string} name
   * @return {boolean}
   */
  hasTask(name: string): boolean {
    return this._tasks.hasTask(name);
  }

  /**
   * 同时注册多个originalTask依赖的task到module中,并创建任务之间的依赖
   * 即不需要再依次再对每个depends task 单独注册到task容器中
   *
   * @param {CoreTask} originalTask 源task
   * @param {CoreTask[]} dependsOnTasks 依赖task
   */
  registryDependsOnTask(originalTask: CoreTask, ...dependsOnTasks: CoreTask[]): void {
    this.taskMap.set(originalTask, dependsOnTasks);
    dependsOnTasks.forEach((task) => {
      originalTask.dependsOn(task);
    });
  }

  /**
   * 获取当前Node的任务容器
   *
   * @return {TaskContainer}
   */
  getTaskContainer(): TaskContainer {
    return this._tasks;
  }

  /**
   * 获取当前Node的任务DAG图对象
   *
   * @return {TaskDirectedAcyclicGraph}
   */
  getTaskGraph(): TaskDirectedAcyclicGraph {
    return this._taskGraph;
  }

  clearTaskGraph() {
    this._taskGraph.clear();
  }

  /**
   * 根据ModuleName获取Module模型
   *
   * @param {string} moduleName 模块名
   * @return {Module | undefined}
   */
  abstract findModuleByName(moduleName: string): Module | undefined;

  /**
   * 获取根项目的Project对象
   *
   * @return {Project}
   */
  abstract getProject(): Project;

  loadConfig(config: any) {
    this.configOpt = config;
  }

  getConfigOpt(): Config {
    return new CoreConfig(this.configOpt);
  }

  addExtraOption(key: string, value: any): void {
    this.extraOption.set(key, value);
  }

  getExtraOption(key: string): any {
    return this.extraOption.get(key);
  }

  beforeNodeEvaluate(fn: Consumer<HvigorNode>): void {
    this.beforeNodeEvaluateFnQueue.push(fn);
  }

  afterNodeEvaluate(fn: Consumer<HvigorNode>): void {
    this.afterNodeEvaluateFnQueue.push(fn);
  }

  async executeNodeHook(hookType: HookType.beforeNodeEvaluate | HookType.afterNodeEvaluate): Promise<void> {
    let fnQueue: Queue<Consumer<HvigorNode>>;
    if (hookType === HookType.beforeNodeEvaluate) {
      fnQueue = this.beforeNodeEvaluateFnQueue;
    } else if (hookType === HookType.afterNodeEvaluate) {
      fnQueue = this.afterNodeEvaluateFnQueue;
    } else {
      this.logger.printErrorExit('HOOK_TYPE_ERROR', [this.getNodeDir()]);
      return;
    }

    while (fnQueue.size() > 0) {
      const fn = fnQueue.pop();
      const hvigorNode = hvigorCore.getHvigorNodeByScriptPath(this._buildFilePath);
      if (hvigorNode === undefined) {
        this.logger.printErrorExit('CANNOT_FOUND_NODE_BUILD_FILE_PATH', [this._buildFilePath]);
        return;
      }
      try {
        fn && (await fn.bind(this)(hvigorNode));
      } catch (e: any) {
        this.logger.printErrorExit('FAILED_TO_EXECUTE_NODE_HOOK', [hookType, e.message], [[hookType]], e.stack);
      }
    }
  }

  afterBindSystemPlugins(fn: Consumer<HvigorNode>): void {
    this.afterBindSystemPluginsFnQueue.push(fn);
  }

  async executeAfterBindSystemPluginsHook(): Promise<void> {
    while (this.afterBindSystemPluginsFnQueue.size() > 0) {
      const fn = this.afterBindSystemPluginsFnQueue.pop();
      const hvigorNode = hvigorCore.getHvigorNodeByScriptPath(this._buildFilePath);
      if (hvigorNode === undefined) {
        this.logger.printErrorExit('CANNOT_FOUND_NODE_BUILD_FILE_PATH', [this._buildFilePath]);
        return;
      }
      fn && (await fn.bind(this)(hvigorNode));
    }
  }
}
